/*
 * http_c2.cpp
 *
 *  Created on: Oct 24, 2013
 *      Author: killerg
 */

#include "http_c2.h"
#include <string>
#include <string.h>
#include <algorithm>

struct data_t {
    std::string recv_buffer;
    std::string send_buffer;
    size_t send_len;
    http_client2::client_cb_t cb;
};

static size_t read_cb( char *ptr, size_t size, size_t nmemb, void *userdata) {
    auto data = (data_t*)userdata;
    size_t total_size = size*nmemb;

    if (data->send_len < data->send_buffer.size()) {
        total_size = std::min(total_size, data->send_buffer.length() - data->send_len);
        memcpy(ptr, data->send_buffer.c_str() + data->send_len, total_size);
        data->send_len += total_size;
        return total_size;
    }

    return 0;
}
static size_t write_cb( char *ptr, size_t size, size_t nmemb, void *userdata) {
    auto data = (data_t*)userdata;
    size_t total_size = size*nmemb;
    data->recv_buffer.append(ptr, total_size);
    
    return total_size;
}

bool http_client2::do_query(const char* url, const char* body, client_cb_t cb) {
    auto data = new data_t;
    data->cb = cb;
    if (body != NULL)
        data->send_buffer.append(body);
    data->send_len = 0;
    CURL *http_handle = curl_easy_init();
    curl_easy_setopt(http_handle, CURLOPT_URL, url);
    curl_easy_setopt(http_handle, CURLOPT_PRIVATE, data);
    curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(http_handle, CURLOPT_READFUNCTION, read_cb);
    curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, data);
    curl_easy_setopt(http_handle, CURLOPT_READDATA, data);
    if (body != NULL) {
        curl_easy_setopt(http_handle, CURLOPT_POST, 1);
        curl_easy_setopt(http_handle, CURLOPT_POSTFIELDSIZE, data->send_buffer.length());
    } else {
        curl_easy_setopt(http_handle, CURLOPT_HTTPGET, 1);
    }
    CURLMcode rt = curl_multi_add_handle(multi_handle, http_handle);

    ++still_running;
    return rt == CURLM_OK;
}

bool http_client2::init() {
    curl_global_init(CURL_GLOBAL_NOTHING);
    multi_handle = curl_multi_init();
    if (NULL == multi_handle)
        return false;

    still_running = 0;
    return true;
}

void http_client2::destroy() {
    if (NULL != multi_handle) {
        curl_multi_cleanup(multi_handle);
        multi_handle = NULL;
    }
    curl_global_cleanup();
}

void http_client2::update() {
    if (still_running <= 0)
        return;

    struct timeval timeout;
    int rc; /* select() return code */

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = -1;

    long curl_timeo = -1;

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    /* set a suitable timeout to play around with */
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    curl_multi_timeout(multi_handle, &curl_timeo);
    if(curl_timeo >= 0) {
        timeout.tv_sec = curl_timeo / 1000;
        if(timeout.tv_sec > 1)
            timeout.tv_sec = 1;
        else
            timeout.tv_usec = (curl_timeo % 1000) * 1000;
    }

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    /* In a real-world program you OF COURSE check the return code of the
    function calls.  On success, the value of maxfd is guaranteed to be
    greater or equal than -1.  We call select(maxfd + 1, ...), specially in
    case of (maxfd == -1), we call select(0, ...), which is basically equal
    to sleep. */

    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

    switch(rc) {
        case -1:
            /* select error */
            break;
        case 0:
        default:
            /* timeout or readable/writable sockets */
            curl_multi_perform(multi_handle, &still_running);
            CURLMsg* msg = NULL;
            int num = 0;
            while ((msg = curl_multi_info_read(multi_handle, &num)) != NULL) {
                data_t* data = NULL;
                if (CURLE_OK == curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &data)) {
                    long code = 0;
                    if (CURLE_OK == curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code))
                        data->cb(code, data->recv_buffer);
                }
                curl_easy_cleanup(msg->easy_handle);
            }
        break;
    }

}
